/*
* 
* @license	Apache License 2.0
* @author	Eugene Kuznetsov
*
*/
#include "CDevice.hpp"
#include "CDataBuffer.hpp"
#include "CConfiguration.hpp"
#include "CDoubleBufferQueue.hpp"
#include <winsock2.h>

CDevice::CDevice(Int32 link, std::string ip_address, UInt8 port)
	: m_eDeviceType(Device::UNKNOWN)
	, m_eState(Link::CONNECTED)
	, m_sIpAddress(ip_address)
	, m_nPort(port)
	, m_nLink(link)
	, m_nLastError(0)
	, m_nID(0)
	, m_bAuthorized(false)
	, m_pConfig(CConfiguration::getInstance())
	, m_pBufferQueues(0)
{
	DBG_L1(("[%p] %s(%d, %s, %d)\n", this, __FUNCTION__, link, m_sIpAddress.c_str(), m_nPort));
	if (link == INVALID_SOCKET)
	{
		m_eState = Link::LINK_ERROR;
	}
}

CDevice::~CDevice()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_eState == Link::CONNECTED)
	{
		Link::eConnectState connect_state = Disconnect();
		if (connect_state != Link::DISCONNECTED)
		{
			DBG_L2(("[%p] connect_state(%d) != Link::DISCONNECTED\n", this, ToInt8(connect_state)));
		}
	}
	if (m_pBufferQueues != 0)
	{
		delete [] m_pBufferQueues;
	}
}

Base::eState CDevice::Authorize(UInt8 timeout)
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	Base::eState state = Base::STATE_UNKNOWN, recv_state = Base::STATE_UNKNOWN;
	CDataBuffer buffer;
	// 1 IsReadyToReceive call = 25ms, 40 calls of IsReadyToReceive = 1 second
	UInt16 ms_timeout = timeout * 25 * 40;
	for (; ms_timeout > 0; --ms_timeout)
	{
		bool ready = IsReadyToReceive(recv_state);
		if ((recv_state != Base::STATE_OK) || !ready)
		{
			continue;
		}
		if (ReceiveDataFromDevice(buffer) != Link::READ_OK)
		{
			DBG_L2(("[%p] authorization failed -> receive error\n", this));
			break;
		}
		m_nID = FetchID(&buffer, m_eDeviceType);
		if (m_eDeviceType == Device::UNKNOWN)
		{
			DBG_L2(("[%p] authorization failed -> unknown device\n", this));
			break;
		}
		DBG_L1(("[%p] trying to authorize device [type:%d] with ID: %llu\n", this, ToUInt8(m_eDeviceType), m_nID));
		Base::eState config_state;
		bool allowed = m_pConfig->IsDeviceAllowed(config_state, m_nID, m_eDeviceType);
		if (config_state != Base::STATE_OK)
		{
			DBG_L2(("[%p] authorization failed -> configuration error (%d)\n", this, ToUInt8(config_state)));
			break;
		}
		if (!allowed)
		{
			DBG_L2(("[%p] authorization failed -> device is not allowed\n", this));
			break;
		}
		DBG_L2(("[%p] authorization granted\n", this));
		state = Base::STATE_OK;
		m_bAuthorized = true;
		break;
	}
	return state;
}

Base::eState CDevice::StartTargetServers(UInt8 timeout)
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	Base::eState state = Base::STATE_UNKNOWN;
	m_pBufferQueues = new CDoubleBufferQueue[MAX_TARGETSERVER_COUNT];
	return state;
}

Link::eConnectState CDevice::Disconnect()
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] not connected -> m_eState = %d\n", this, ToUInt8(m_eState)));
		return m_eState;
	}
	m_eState = Link::DISCONNECTING;
	Int16 result = shutdown(m_nLink, SD_BOTH);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] shutdown call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LINK_ERROR;
	}
	result = closesocket(m_nLink);
	if (result == SOCKET_ERROR)
	{
		DBG_L2(("[%p] closesocket call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		m_eState = Link::LINK_ERROR;
	}
	else
	{
		DBG_L2(("[%p] connection(%d) with %s:%d has been closed\n", this, m_nLink, m_sIpAddress.c_str(), m_nPort));
		m_eState = Link::DISCONNECTED;
		m_nLink = INVALID_SOCKET;
	}
	return m_eState;
}

Link::eLinkIOState CDevice::SendDataToDevice(CDataBuffer* buffer)
{
	DBG_L1(("[%p] %s -> send %d bytes\n", this, __FUNCTION__, buffer->size()));
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] link state != Link::CONNECTED\n", this));
		return Link::UNKNOWN_ERROR;
	}
	Int16 bytes = send(m_nLink, reinterpret_cast<const char*>(buffer->data()), buffer->size(), 0);
	if (bytes == -1)
	{
		DBG_L2(("[%p] send call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		return Link::WRITE_ERROR;
	}
	else
	{
		DBG_L3(("[%p] %d bytes has been sent\n", this, bytes));
	}
	return (bytes == 0) ? Link::WRITE_CLOSE : Link::WRITE_OK;
}

Link::eLinkIOState CDevice::ReceiveDataFromDevice(CDataBuffer& buffer)
{
	DBG_L1(("[%p] %s\n", this, __FUNCTION__));
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] link state != Link::CONNECTED\n", this));
		return Link::UNKNOWN_ERROR;
	}
	UChr temp_buffer[LINK_BUFFER_SIZE];
	memset(temp_buffer, 0, LINK_BUFFER_SIZE);
	Int16 bytes = recv(m_nLink, reinterpret_cast<char*>(temp_buffer), LINK_BUFFER_SIZE, 0);
	if (bytes == -1)
	{
		DBG_L2(("[%p] recv call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
		return Link::READ_ERROR;
	}
	else
	{
		DBG_L3(("[%p] %d bytes has been received\n", this, bytes));
		if (bytes > 0)
		{
			buffer.append(temp_buffer, ToUInt32(bytes));
		}
	}
	return (bytes == 0) ? Link::READ_CLOSE : Link::READ_OK;
}

bool CDevice::IsReadyToReceive(Base::eState& state)
{
	DBG_L4(("[%p] %s\n", this, __FUNCTION__));
	if (m_eState != Link::CONNECTED)
	{
		DBG_L2(("[%p] link state != Link::CONNECTED\n", this));
		return false;
	}
	timeval timeout = {0, 25};
	fd_set fd_read;
	FD_ZERO(&fd_read);
	FD_SET(m_nLink, &fd_read);
	Int16 result = select(0, &fd_read, NULL, NULL, &timeout);
	switch (result)
	{
		case SOCKET_ERROR:
			DBG_L2(("[%p] select call -> err:%d\n", this, (m_nLastError = WSAGetLastError())));
			state = Base::STATE_NOT_OK;
			return false;
		case 0:
			DBG_L4(("[%p] not ready to read\n", this));
			state = Base::STATE_OK;
			return false;
		case 1:
			DBG_L4(("[%p] ready to read\n", this));
			state = Base::STATE_OK;
			return true;
		default:
			state = Base::STATE_UNKNOWN;
			return true;
	}
}

Link::eConnectState CDevice::getState() const
{
	return m_eState;
}

Int16 CDevice::getLastError()
{
	Int16 err = m_nLastError;
	m_nLastError = 0;
	return err;
}

UInt64 CDevice::FetchID(CDataBuffer* buffer, Device::eType& dev_type)
{
	UInt64 nID = 0;
	dev_type = Device::UNKNOWN;
	const UChr * p = buffer->data();
	if (buffer->size() == 8)
	{
		// IntelliTrack sync
		if (p[0] == 0xFA && p[1] == 0xF8)
		{
			p = &(buffer->data())[7];
			nID = (*p << 24) + (*(p-1) << 16) + (*(p-2) << 8) + *(p-3);
			dev_type = Device::INTELLITRAC;
		}
	}
	else if (buffer->size() == 17)
	{
		// Teltonika IMEI packet
		if (p[0] == 0x00 && p[1] == 0x0F)
		{
			Chr c_id[16];
			for (int i = 2, j = 0; i < 17; ++i)
			{
				c_id[j++] = p[i];
			}
			nID = _atoi64(c_id);
			dev_type = Device::TELTONIKA;
		}
	}
	return nID;
}
